<!DOCTYPE html><html lang="zh-CN" data-theme="light"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><title>【JDK源码笔记】- 别走！这里有个笔记：图文讲解 AQS ，一起看看 AQS 的源码……(图文较长) | 程序员小航</title><meta name="keywords" content="源码笔记,JDK"><meta name="author" content="liuzhihang"><meta name="copyright" content="liuzhihang"><meta name="format-detection" content="telephone=no"><meta name="theme-color" content="#ffffff"><meta name="description" content="AbstractQueuedSynchronizer 抽象队列同步器，简称 AQS 。是在 JUC 包下面一个非常重要的基础组件，JUC 包下面的并发锁 &#96;ReentrantLock&#96; &#96;CountDownLatch&#96; 等都是基于 AQS 实现的。所以想进一步研究锁的底层原理，非常有必要先了解 AQS 的原理。">
<meta property="og:type" content="article">
<meta property="og:title" content="【JDK源码笔记】- 别走！这里有个笔记：图文讲解 AQS ，一起看看 AQS 的源码……(图文较长)">
<meta property="og:url" content="https://liuzhihang.com/2020/07/12/source-code-aqs.html">
<meta property="og:site_name" content="程序员小航">
<meta property="og:description" content="AbstractQueuedSynchronizer 抽象队列同步器，简称 AQS 。是在 JUC 包下面一个非常重要的基础组件，JUC 包下面的并发锁 &#96;ReentrantLock&#96; &#96;CountDownLatch&#96; 等都是基于 AQS 实现的。所以想进一步研究锁的底层原理，非常有必要先了解 AQS 的原理。">
<meta property="og:locale" content="zh_CN">
<meta property="og:image" content="https://cdn.jsdelivr.net/gh/liuzhihang/oss/pic/article/aqs-uhgQnu.png">
<meta property="article:published_time" content="2020-07-12T05:00:00.000Z">
<meta property="article:modified_time" content="2020-12-27T09:23:14.251Z">
<meta property="article:author" content="liuzhihang">
<meta property="article:tag" content="源码笔记">
<meta property="article:tag" content="JDK">
<meta name="twitter:card" content="summary">
<meta name="twitter:image" content="https://cdn.jsdelivr.net/gh/liuzhihang/oss/pic/article/aqs-uhgQnu.png"><link rel="shortcut icon" href="https://cdn.jsdelivr.net/gh/liuzhihang/oss/pic/favicon.ico"><link rel="canonical" href="https://liuzhihang.com/2020/07/12/source-code-aqs"><link rel="preconnect" href="//cdn.jsdelivr.net"/><link rel="preconnect" href="//www.google-analytics.com" crossorigin=""/><link rel="preconnect" href="//pingjs.qq.com"/><link rel="preconnect" href="//busuanzi.ibruce.info"/><meta name="google_site_verification" content="GZshAnnrC-4eCrl5h-e_5Rdk4lOUhRK7ShULoRi2q0E"/><link rel="stylesheet" href="/css/index.css"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free/css/all.min.css" media="print" onload="this.media='all'"><script async="async" src="https://www.googletagmanager.com/gtag/js?id=UA-126394362-1"></script><script>window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'UA-126394362-1');
</script><script>var _mtac = {};
(function() {
    var mta = document.createElement("script");
    mta.src = "//pingjs.qq.com/h5/stats.js?v2.0.4";
    mta.setAttribute("name", "MTAH5");
    mta.setAttribute("sid", "66540676");
    var s = document.getElementsByTagName("script")[0];
    s.parentNode.insertBefore(mta, s);
})();
</script><script>const GLOBAL_CONFIG = { 
  root: '/',
  algolia: undefined,
  localSearch: {"path":"search.xml","languages":{"hits_empty":"找不到您查询的内容：${query}"}},
  translate: {"defaultEncoding":2,"translateDelay":0,"msgToTraditionalChinese":"繁","msgToSimplifiedChinese":"簡"},
  noticeOutdate: {"limitDay":730,"position":"top","messagePrev":"It has been","messageNext":"days since the last update, the content of the article may be outdated."},
  highlight: undefined,
  copy: {
    success: '复制成功',
    error: '复制错误',
    noSupport: '浏览器不支持'
  },
  relativeDate: {
    homepage: false,
    post: false
  },
  runtime: '',
  date_suffix: {
    just: '刚刚',
    min: '分钟前',
    hour: '小时前',
    day: '天前',
    month: '个月前'
  },
  copyright: undefined,
  lightbox: 'fancybox',
  Snackbar: undefined,
  source: {
    jQuery: 'https://cdn.jsdelivr.net/npm/jquery@latest/dist/jquery.min.js',
    justifiedGallery: {
      js: 'https://cdn.jsdelivr.net/npm/justifiedGallery/dist/js/jquery.justifiedGallery.min.js',
      css: 'https://cdn.jsdelivr.net/npm/justifiedGallery/dist/css/justifiedGallery.min.css'
    },
    fancybox: {
      js: 'https://cdn.jsdelivr.net/npm/@fancyapps/fancybox@latest/dist/jquery.fancybox.min.js',
      css: 'https://cdn.jsdelivr.net/npm/@fancyapps/fancybox@latest/dist/jquery.fancybox.min.css'
    }
  },
  isPhotoFigcaption: false,
  islazyload: true,
  isanchor: false
}</script><script id="config-diff">var GLOBAL_CONFIG_SITE = {
  title: '【JDK源码笔记】- 别走！这里有个笔记：图文讲解 AQS ，一起看看 AQS 的源码……(图文较长)',
  isPost: true,
  isHome: false,
  isHighlightShrink: false,
  isToc: true,
  postUpdate: '2020-12-27 17:23:14'
}</script><noscript><style type="text/css">
  #nav {
    opacity: 1
  }
  .justified-gallery img {
    opacity: 1
  }

  #recent-posts time,
  #post-meta time {
    display: inline !important
  }
</style></noscript><script>(win=>{
    win.saveToLocal = {
      set: function setWithExpiry(key, value, ttl) {
        if (ttl === 0) return
        const now = new Date()
        const expiryDay = ttl * 86400000
        const item = {
          value: value,
          expiry: now.getTime() + expiryDay,
        }
        localStorage.setItem(key, JSON.stringify(item))
      },

      get: function getWithExpiry(key) {
        const itemStr = localStorage.getItem(key)

        if (!itemStr) {
          return undefined
        }
        const item = JSON.parse(itemStr)
        const now = new Date()

        if (now.getTime() > item.expiry) {
          localStorage.removeItem(key)
          return undefined
        }
        return item.value
      }
    }
  
    win.getScript = url => new Promise((resolve, reject) => {
      const script = document.createElement('script')
      script.src = url
      script.async = true
      script.onerror = reject
      script.onload = script.onreadystatechange = function() {
        const loadState = this.readyState
        if (loadState && loadState !== 'loaded' && loadState !== 'complete') return
        script.onload = script.onreadystatechange = null
        resolve()
      }
      document.head.appendChild(script)
    })
  
      win.activateDarkMode = function () {
        document.documentElement.setAttribute('data-theme', 'dark')
        if (document.querySelector('meta[name="theme-color"]') !== null) {
          document.querySelector('meta[name="theme-color"]').setAttribute('content', '#0d0d0d')
        }
      }
      win.activateLightMode = function () {
        document.documentElement.setAttribute('data-theme', 'light')
        if (document.querySelector('meta[name="theme-color"]') !== null) {
          document.querySelector('meta[name="theme-color"]').setAttribute('content', '#ffffff')
        }
      }
      const t = saveToLocal.get('theme')
    
          if (t === 'dark') activateDarkMode()
          else if (t === 'light') activateLightMode()
        
      const asideStatus = saveToLocal.get('aside-status')
      if (asideStatus !== undefined) {
        if (asideStatus === 'hide') {
          document.documentElement.classList.add('hide-aside')
        } else {
          document.documentElement.classList.remove('hide-aside')
        }
      }
    
    const detectApple = () => {
      if (GLOBAL_CONFIG_SITE.isHome && /iPad|iPhone|iPod|Macintosh/.test(navigator.userAgent)){
        document.documentElement.classList.add('apple')
      }
    }
    detectApple()
    })(window)</script><link rel="stylesheet" href="//at.alicdn.com/t/font_1608878_xsf93f0c1a.css"><meta name="generator" content="Hexo 5.2.0"><link rel="alternate" href="/atom.xml" title="程序员小航" type="application/atom+xml">
</head><body><div id="sidebar"><div id="menu-mask"></div><div id="sidebar-menus"><div class="avatar-img is-center"><img src= "https://cdn.jsdelivr.net/gh/liuzhihang/oss/pic/loading.gif" data-lazy-src="https://cdn.jsdelivr.net/gh/liuzhihang/oss/pic/404/avatar.jpg" onerror="onerror=null;src='/img/friend_404.gif'" alt="avatar"/></div><div class="site-data"><div class="data-item is-center"><div class="data-item-link"><a href="/archives/"><div class="headline">文章</div><div class="length-num">145</div></a></div></div><div class="data-item is-center"><div class="data-item-link"><a href="/tags/"><div class="headline">标签</div><div class="length-num">55</div></a></div></div><div class="data-item is-center"><div class="data-item-link"><a href="/categories/"><div class="headline">分类</div><div class="length-num">31</div></a></div></div></div><hr/><div class="menus_items"><div class="menus_item"><a class="site-page" href="/"><i class="fa-fw fas fa-home"></i><span> 首页</span></a></div><div class="menus_item"><a class="site-page" href="javascript:void(0);"><i class="fa-fw fas fa-layer-group"></i><span> 导航</span><i class="fas fa-chevron-down expand hide"></i></a><ul class="menus_item_child"><li><a class="site-page child" href="/archives/"><i class="fa-fw fas fa-archive"></i><span> 归档</span></a></li><li><a class="site-page child" href="/tags/"><i class="fa-fw fas fa-tags"></i><span> 标签</span></a></li><li><a class="site-page child" href="/categories/"><i class="fa-fw fas fa-bookmark"></i><span> 分类</span></a></li></ul></div><div class="menus_item"><a class="site-page" href="javascript:void(0);"><i class="fa-fw fas fa-list"></i><span> 推荐</span><i class="fas fa-chevron-down expand hide"></i></a><ul class="menus_item_child"><li><a class="site-page child" href="/books/"><i class="fa-fw fas fa-book"></i><span> 书单</span></a></li><li><a class="site-page child" href="/movies/"><i class="fa-fw fas fa-film"></i><span> 电影</span></a></li><li><a class="site-page child" href="/games/"><i class="fa-fw fas fa-gamepad"></i><span> 游戏</span></a></li></ul></div><div class="menus_item"><a class="site-page" href="/contact/"><i class="fa-fw fas fa-comments"></i><span> 留言板</span></a></div><div class="menus_item"><a class="site-page" href="/link/"><i class="fa-fw fas fa-address-book"></i><span> 友链</span></a></div><div class="menus_item"><a class="site-page" href="/about/"><i class="fa-fw fas fa-user-circle"></i><span> 关于</span></a></div><div class="menus_item"><a class="site-page" href="javascript:void(0);"><i class="fa-fw fas fa-list"></i><span> 其他</span><i class="fas fa-chevron-down expand hide"></i></a><ul class="menus_item_child"><li><a class="site-page child" target="_blank" rel="noopener" href="https://juejin.im/user/1987506650493117"><i class="fa-fw iconfont iconjuejin-1"></i><span> 掘金</span></a></li><li><a class="site-page child" target="_blank" rel="noopener" href="https://www.zhihu.com/people/liuzhihang"><i class="fa-fw iconfont iconzhihu1"></i><span> 知乎</span></a></li><li><a class="site-page child" target="_blank" rel="noopener" href="https://www.infoq.cn/profile/00B8AE08DA916E/publish"><i class="fa-fw iconfont iconweibiaoti-1"></i><span> InfoQ</span></a></li><li><a class="site-page child" target="_blank" rel="noopener" href="https://blog.csdn.net/qq_36535538"><i class="fa-fw iconfont iconcsdn"></i><span> CSDN</span></a></li><li><a class="site-page child" target="_blank" rel="noopener" href="https://leetcode-cn.com/u/liuzhihang/"><i class="fa-fw iconfont iconleetcode"></i><span> LeetCode</span></a></li></ul></div></div></div></div><div class="post" id="body-wrap"><header class="post-bg" id="page-header" style="background-image: url('https://cdn.jsdelivr.net/gh/liuzhihang/oss/pic/article/aqs-uhgQnu.png')"><nav id="nav"><span id="blog_name"><a id="site-name" href="/">程序员小航</a></span><div id="menus"><div id="search-button"><a class="site-page social-icon search"><i class="fas fa-search fa-fw"></i><span> 搜索</span></a></div><div class="menus_items"><div class="menus_item"><a class="site-page" href="/"><i class="fa-fw fas fa-home"></i><span> 首页</span></a></div><div class="menus_item"><a class="site-page" href="javascript:void(0);"><i class="fa-fw fas fa-layer-group"></i><span> 导航</span><i class="fas fa-chevron-down expand hide"></i></a><ul class="menus_item_child"><li><a class="site-page child" href="/archives/"><i class="fa-fw fas fa-archive"></i><span> 归档</span></a></li><li><a class="site-page child" href="/tags/"><i class="fa-fw fas fa-tags"></i><span> 标签</span></a></li><li><a class="site-page child" href="/categories/"><i class="fa-fw fas fa-bookmark"></i><span> 分类</span></a></li></ul></div><div class="menus_item"><a class="site-page" href="javascript:void(0);"><i class="fa-fw fas fa-list"></i><span> 推荐</span><i class="fas fa-chevron-down expand hide"></i></a><ul class="menus_item_child"><li><a class="site-page child" href="/books/"><i class="fa-fw fas fa-book"></i><span> 书单</span></a></li><li><a class="site-page child" href="/movies/"><i class="fa-fw fas fa-film"></i><span> 电影</span></a></li><li><a class="site-page child" href="/games/"><i class="fa-fw fas fa-gamepad"></i><span> 游戏</span></a></li></ul></div><div class="menus_item"><a class="site-page" href="/contact/"><i class="fa-fw fas fa-comments"></i><span> 留言板</span></a></div><div class="menus_item"><a class="site-page" href="/link/"><i class="fa-fw fas fa-address-book"></i><span> 友链</span></a></div><div class="menus_item"><a class="site-page" href="/about/"><i class="fa-fw fas fa-user-circle"></i><span> 关于</span></a></div><div class="menus_item"><a class="site-page" href="javascript:void(0);"><i class="fa-fw fas fa-list"></i><span> 其他</span><i class="fas fa-chevron-down expand hide"></i></a><ul class="menus_item_child"><li><a class="site-page child" target="_blank" rel="noopener" href="https://juejin.im/user/1987506650493117"><i class="fa-fw iconfont iconjuejin-1"></i><span> 掘金</span></a></li><li><a class="site-page child" target="_blank" rel="noopener" href="https://www.zhihu.com/people/liuzhihang"><i class="fa-fw iconfont iconzhihu1"></i><span> 知乎</span></a></li><li><a class="site-page child" target="_blank" rel="noopener" href="https://www.infoq.cn/profile/00B8AE08DA916E/publish"><i class="fa-fw iconfont iconweibiaoti-1"></i><span> InfoQ</span></a></li><li><a class="site-page child" target="_blank" rel="noopener" href="https://blog.csdn.net/qq_36535538"><i class="fa-fw iconfont iconcsdn"></i><span> CSDN</span></a></li><li><a class="site-page child" target="_blank" rel="noopener" href="https://leetcode-cn.com/u/liuzhihang/"><i class="fa-fw iconfont iconleetcode"></i><span> LeetCode</span></a></li></ul></div></div><div id="toggle-menu"><a class="site-page"><i class="fas fa-bars fa-fw"></i></a></div></div></nav><div id="post-info"><h1 class="post-title">【JDK源码笔记】- 别走！这里有个笔记：图文讲解 AQS ，一起看看 AQS 的源码……(图文较长)</h1><div id="post-meta"><div class="meta-firstline"><span class="post-meta-date"><i class="far fa-calendar-alt fa-fw post-meta-icon"></i><span class="post-meta-label">发表于</span><time class="post-meta-date-created" datetime="2020-07-12T05:00:00.000Z" title="发表于 2020-07-12 13:00:00">2020-07-12</time><span class="post-meta-separator">|</span><i class="fas fa-history fa-fw post-meta-icon"></i><span class="post-meta-label">更新于</span><time class="post-meta-date-updated" datetime="2020-12-27T09:23:14.251Z" title="更新于 2020-12-27 17:23:14">2020-12-27</time></span><span class="post-meta-categories"><span class="post-meta-separator">|</span><i class="fas fa-inbox fa-fw post-meta-icon"></i><a class="post-meta-categories" href="/categories/%E6%BA%90%E7%A0%81%E7%AC%94%E8%AE%B0/">源码笔记</a><i class="fas fa-angle-right post-meta-separator"></i><i class="fas fa-inbox fa-fw post-meta-icon"></i><a class="post-meta-categories" href="/categories/%E6%BA%90%E7%A0%81%E7%AC%94%E8%AE%B0/JDK/">JDK</a></span></div><div class="meta-secondline"><span class="post-meta-separator">|</span><span class="post-meta-wordcount"><i class="far fa-file-word fa-fw post-meta-icon"></i><span class="post-meta-label">字数总计:</span><span class="word-count">6k</span><span class="post-meta-separator">|</span><i class="far fa-clock fa-fw post-meta-icon"></i><span class="post-meta-label">阅读时长:</span><span>21分钟</span></span><span class="post-meta-separator">|</span><span class="post-meta-pv-cv" id="" data-flag-title="【JDK源码笔记】- 别走！这里有个笔记：图文讲解 AQS ，一起看看 AQS 的源码……(图文较长)"><i class="far fa-eye fa-fw post-meta-icon"></i><span class="post-meta-label">阅读量:</span><span id="busuanzi_value_page_pv"></span></span><span class="post-meta-separator">|</span><span class="post-meta-commentcount"><i class="far fa-comments fa-fw post-meta-icon"></i><span class="post-meta-label">评论数:</span><a href="/2020/07/12/source-code-aqs.html#post-comment"><span class="gitalk-comment-count"></span></a></span></div></div></div></header><main class="layout" id="content-inner"><div id="post"><article class="post-content" id="article-container"><blockquote>
<p>AbstractQueuedSynchronizer 抽象队列同步器，简称 AQS 。是在 JUC 包下面一个非常重要的基础组件，JUC 包下面的并发锁 <code>ReentrantLock</code> <code>CountDownLatch</code> 等都是基于 AQS 实现的。所以想进一步研究锁的底层原理，非常有必要先了解 AQS 的原理。</p>
</blockquote>
<h3 id="介绍"><a href="#介绍" class="headerlink" title="介绍"></a>介绍</h3><p>先看下 AQS 的类图结构，以及源码注释，有一定的大概了解之后再从源码入手，一步一步研究它的底层原理。</p>
<p><img src= "https://cdn.jsdelivr.net/gh/liuzhihang/oss/pic/loading.gif" data-lazy-src="https://cdn.jsdelivr.net/gh/liuzhihang/oss/pic/article/aqs.png" alt="aqs"></p>
<p>“ 源码注释</p>
<p>提供了实现阻塞锁和相关同步器依靠先入先出（FIFO）等待队列（信号量，事件等）的框架。 此类中设计了一个对大多数基于 AQS 的同步器有用的原子变量来表示状态（state）。 子类必须定义 protected 方法来修改这个 state，并且定义 state 值在对象中的具体含义是 acquired 或 released。 考虑到这些，在这个类中的其他方法可以实现所有排队和阻塞机制。 子类可以保持其他状态字段，但只能使用方法 getState 、setState 和 compareAndSetState 以原子方式更新 state 。</p>
<p>子类应被定义为用于实现其封闭类的同步性能的非公共内部辅助类。 类AbstractQueuedSynchronizer没有实现任何同步接口。 相反，它定义了一些方法，如 acquireInterruptibly 可以通过具体的锁和相关同步器来调用适当履行其公共方法。</p>
<p>此类支持独占模式和共享模式。 在独占模式下，其他线程不能获取成功，共享模式下可以（但不一定）获取成功。 此类不“理解”，在机械意义上这些不同的是，当共享模式获取成功，则下一个等待的线程（如果存在）也必须确定它是否能够获取。 线程在不同模式下的等待共享相同的FIFO队列。 通常情况下，实现子类只支持其中一种模式，但同时使用两种模式也可以，例如ReadWriteLock 。 仅共享模式不需要定义支持未使用的模式的方法的子类。</p>
<p>这个类中定义了嵌套类 AbstractQueuedSynchronizer.ConditionObject ，可用于作为一个 Condition 由子类实现，并使用 isHeldExclusively 方法说明当前线程是否以独占方式进行，release()、 getState() acquire() 方法用于操作 state 原子变量。</p>
<p>此类提供检查和监视内部队列的方法，以及类似方法的条件对象。 根据需要进使用以用于它们的同步机制。</p>
<p>要使用这个类用作同步的基础上，需要重新定义以下方法，如使用，通过检查和或修改 getState 、setState 或 compareAndSetState 方法：</p>
<p>tryAcquire<br>tryRelease<br>tryAcquireShared<br>tryReleaseShared<br>isHeldExclusively</p>
<p>“</p>
<p>通过上面的注释可以得出大概的印象：</p>
<ol>
<li>内部依靠先入先出（FIFO） 等待队列。</li>
<li>存在 state 表示状态信息。state 值只能用 getState 、setState 和 compareAndSetState 方法以原子方式更新。</li>
<li>支持独占模式和共享模式，但具体需要子类实现具体支持哪种模式。</li>
<li>嵌套 AbstractQueuedSynchronizer.ConditionObject 可以作为 Condition 由子类实现。</li>
<li>子类需要重新定义  tryAcquire、tryRelease、tryAcquireShared、tryReleaseShared、isHeldExclusively 方法。</li>
</ol>
<h3 id="队列节点-Node"><a href="#队列节点-Node" class="headerlink" title="队列节点 Node"></a>队列节点 Node</h3><p><img src= "https://cdn.jsdelivr.net/gh/liuzhihang/oss/pic/loading.gif" data-lazy-src="https://cdn.jsdelivr.net/gh/liuzhihang/oss/pic/article/node-1P32mR.png" alt="node-1P32mR"></p>
<p>Node节点，包含以下元素：</p>
<table>
<thead>
<tr>
<th>元素</th>
<th>含义</th>
</tr>
</thead>
<tbody><tr>
<td>prev</td>
<td>上一个节点</td>
</tr>
<tr>
<td>next</td>
<td>下一个节点</td>
</tr>
<tr>
<td>thread</td>
<td>持有线程</td>
</tr>
<tr>
<td>waitStatus</td>
<td>节点状态</td>
</tr>
<tr>
<td>nextWaiter</td>
<td>下一个处于 CONDITION 状态的节点</td>
</tr>
</tbody></table>
<p>组合成等待队列则如下：</p>
<p><img src= "https://cdn.jsdelivr.net/gh/liuzhihang/oss/pic/loading.gif" data-lazy-src="https://cdn.jsdelivr.net/gh/liuzhihang/oss/pic/article/node-fifo.png" alt="node-fifo"></p>
<p>下面是等待队列节点的 Node 属性：</p>
<pre><code class="java">static final class Node &#123;
    // 节点正在共享模式下等待的标记
    static final Node SHARED = new Node();
    // 指示节点正在以独占模式等待的标记
    static final Node EXCLUSIVE = null;

    // 指示线程已取消
    static final int CANCELLED =  1;
    // 指示后续线程需要唤醒
    static final int SIGNAL    = -1;
    // 指示线程正在等待条件
    static final int CONDITION = -2;
    // 指示下一次acquireShared应该无条件传播
    static final int PROPAGATE = -3;

    /**
     * 状态字段，仅使用以下值
     * SIGNAL -1 ：当前节点释放或者取消时，必须 unpark 他的后续节点。
     * CANCELLED 1 ：由于超时（timeout）或中断（interrupt），该节点被取消。节点永远不会离开此状态。特别是，具有取消节点的线程永远不会再次阻塞。
     * CONDITION -2 ：该节点目前在条件队列。 但它不会被用作同步队列节点，直到转移，转移时的状态将被设置为 0 。
     * PROPAGATE -3 ：releaseShared 应该被传播到其他节点。 
     * 0：都不是
     * 值以数字表示以简化使用，大多数时候可以检查符号（是否大于0）以简化使用
     */
    volatile int waitStatus;

    // 上一个节点
    volatile Node prev;

    // 下一个节点
    volatile Node next;

    // 节点持有线程
    volatile Thread thread;

    // 链接下一个等待条件节点，或特殊值共享
    Node nextWaiter;

    // 节点是否处于 共享状态 是， 返回 true
    final boolean isShared() &#123;
        return nextWaiter == SHARED;
    &#125;

    // 返回前一个节点， 使用时 前一个节点不能为空
    final Node predecessor() throws NullPointerException &#123;
        Node p = prev;
        if (p == null)
            throw new NullPointerException();
        else
            return p;
    &#125;

    Node() &#123;    // Used to establish initial head or SHARED marker
    &#125;

    Node(Thread thread, Node mode) &#123;     // Used by addWaiter
        this.nextWaiter = mode;
        this.thread = thread;
    &#125;

    Node(Thread thread, int waitStatus) &#123; // Used by Condition
        this.waitStatus = waitStatus;
        this.thread = thread;
    &#125;
&#125;</code></pre>
<p>在 Node 节点中需要重点关注 waitStatus</p>
<ol>
<li>默认状态为 0；</li>
<li>waitStatus &gt; 0 (CANCELLED 1) 说明该节点超时或者中断了，需要从队列中移除；</li>
<li>waitStatus = -1 SIGNAL 当前线程的前一个节点的状态为 SIGNAL，则当前线程需要阻塞（unpark）；</li>
<li>waitStatus = -2 CONDITION -2 ：该节点目前在条件队列；</li>
<li>waitStatus = -3 PROPAGATE -3 ：releaseShared 应该被传播到其他节点，在共享锁模式下使用。</li>
</ol>
<p>了解完 Node 的结构之后，再了解下 AQS 结构，并从常用方法入手，逐步了解具体实现逻辑。</p>
<h3 id="AbstractQueuedSynchronizer"><a href="#AbstractQueuedSynchronizer" class="headerlink" title="AbstractQueuedSynchronizer"></a>AbstractQueuedSynchronizer</h3><pre><code class="java">public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer implements java.io.Serializable &#123;

    // 等待队列的头，延迟初始化。 除了初始化，它是仅经由方法setHead修改。 注意：如果头存在，其waitStatus保证不会是 CANCELLED 状态
    private transient volatile Node head;

    // 等待队列的尾部，延迟初始化。 仅在修改通过方法ENQ添加新节点等待。
    private transient volatile Node tail;

    // 同步状态 
    private volatile int state;

    // 获取状态
    protected final int getState() &#123;
        return state;
    &#125;

    // 设置状态值
    protected final void setState(int newState) &#123;
        state = newState;
    &#125;

    // 原子更新状态值
    protected final boolean compareAndSetState(int expect, int update) &#123;
        // See below for intrinsics setup to support this
        return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
    &#125;

&#125;</code></pre>
<p>在 AQS 中主要参数为：</p>
<table>
<thead>
<tr>
<th>参数</th>
<th>含义</th>
</tr>
</thead>
<tbody><tr>
<td>head</td>
<td>等待队列头</td>
</tr>
<tr>
<td>tail</td>
<td>等待队列尾</td>
</tr>
<tr>
<td>state</td>
<td>同步状态</td>
</tr>
</tbody></table>
<p>通过注释了解到，在 AQS 里主要分为两种操作模式，分别是：独占模式、共享模式，下面分别从两个不同的角度去分析源码。</p>
<table>
<thead>
<tr>
<th>操作</th>
<th>含义</th>
</tr>
</thead>
<tbody><tr>
<td>acquire</td>
<td>以独占模式获取，忽略中断。 通过调用至少一次实施tryAcquire ，在成功时返回。 否则，线程排队，可能重复查封和解封，调用tryAcquire直到成功为止。 这种方法可以用来实现方法Lock.lock 。</td>
</tr>
<tr>
<td>release</td>
<td>以独占模式释放。 通过疏通一个或多个线程，如果实现tryRelease返回true。 这种方法可以用来实现方法Lock.unlock 。</td>
</tr>
<tr>
<td>acquireShared</td>
<td>获取在共享模式下，忽略中断。 通过至少一次第一调用实现tryAcquireShared ，在成功时返回。 否则，线程排队，可能重复查封和解封，调用tryAcquireShared直到成功为止。</td>
</tr>
<tr>
<td>releaseShared</td>
<td>以共享模式释放。 通过疏通一个或多个线程，如果实现tryReleaseShared返回true。</td>
</tr>
</tbody></table>
<p>无论是共享模式还是独占模式在这里面都会用到 addWaiter 方法，将当前线程及模式创建排队节点。</p>
<h4 id="独占模式"><a href="#独占模式" class="headerlink" title="独占模式"></a>独占模式</h4><h5 id="获取独占资源-acquire"><a href="#获取独占资源-acquire" class="headerlink" title="获取独占资源 acquire"></a>获取独占资源 acquire</h5><pre><code class="java">
public final void acquire(int arg) &#123;
    // tryAcquire 尝试获取 state，获取失败则会加入到队列
    if (!tryAcquire(arg) &amp;&amp; acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
&#125;</code></pre>
<p>在独占模式下会尝试获取 state，当获取失败时会调用 acquireQueued(addWaiter(Node.EXCLUSIVE), arg)。</p>
<ol>
<li>tryAcquire(arg)，尝试获取 state 这块由子类自己实现，不同的子类逻辑不同，这块在介绍子类代码时会说明。</li>
<li>获取 state 失败后，会进行 acquireQueued(addWaiter(Node.EXCLUSIVE), arg)，这块代码可以拆分为两块：addWaiter(Node.EXCLUSIVE)，acquireQueued(node, arg)。</li>
<li>addWaiter(Node.EXCLUSIVE) 返回的是当前新创建的节点。</li>
<li>acquireQueued(node, arg) 线程获取锁失败，使用 addWaiter(Node.EXCLUSIVE) 放入等待队列，而 acquireQueued(node, arg) 使用循环，不断的为队列中的节点去尝试获取资源，直到获取成功或者被中断。</li>
</ol>
<p>总结获取资源主要分为三步：</p>
<ol>
<li>尝试获取资源</li>
<li>入队列</li>
<li>出队列</li>
</ol>
<p>尝试获取资源 <code>tryAcquire(arg)</code>，由子类实现，那下面则着手分别分析 <code>入队列</code>、<code>出队列</code>。</p>
<h6 id="入队列：addWaiter-Node-EXCLUSIVE"><a href="#入队列：addWaiter-Node-EXCLUSIVE" class="headerlink" title="入队列：addWaiter(Node.EXCLUSIVE)"></a>入队列：addWaiter(Node.EXCLUSIVE)</h6><p>使用 <code>addWaiter(Node.EXCLUSIVE)</code> 方法将节点插入到队列中，步骤如下：</p>
<ol>
<li>根据传入的模式创建节点</li>
<li>判断尾节点是否存在，不存在则需要使用 <code>enq(node)</code> 方法初始化节点，存在则直接<code>尝试</code>插入尾部。</li>
<li><code>尝试</code>插入尾部时使用 CAS 插入，防止并发情况，如果插入失败，会调用 <code>enq(node)</code> 自旋直到插入。</li>
</ol>
<pre><code class="java">private Node addWaiter(Node mode) &#123;
    Node node = new Node(Thread.currentThread(), mode);
    // 定位到队列末尾的 node
    Node pred = tail;
    if (pred != null) &#123;
        // 新节点的上一个节点 指向尾节点
        node.prev = pred;
        // 使用 CAS 设置尾节点，tail 如果等于 pred 则更新为 node
        if (compareAndSetTail(pred, node)) &#123;
            // 更新成功则将 pred 的下一个节点指向 node
            pred.next = node;
            return node;
        &#125;
    &#125;
    // 尾节点没有初始化，或竞争失败，自旋
    enq(node);
    return node;
&#125;

/**
 * tailOffset 也就是成员变量 tail 的值
 * 此处相当于：比较 tail 的值和 expect 的值是否相等， 相等则更新为 update
 */
private final boolean compareAndSetTail(Node expect, Node update) &#123;
    return unsafe.compareAndSwapObject(this, tailOffset, expect, update);
&#125;
private final boolean compareAndSetHead(Node update) &#123;
        return unsafe.compareAndSwapObject(this, headOffset, null, update);
&#125;

private Node enq(final Node node) &#123;
    for (;;) &#123;
        Node t = tail;
        // 尾节点为空 需要初始化头节点，此时头尾节点是一个
        if (t == null) &#123; // Must initialize
            if (compareAndSetHead(new Node()))
                tail = head;
        &#125; else &#123;
            // 不为空 循环赋值
            node.prev = t;
            if (compareAndSetTail(t, node)) &#123;
                t.next = node;
                return t;
            &#125;
        &#125;
    &#125;
&#125;</code></pre>
<p>看完代码和注释肯定还是有点模糊，现在用图一步一步进行说明。</p>
<p>因为根据<code>初始尾节点是否为空</code>分为两种情况，这里使用两幅图：</p>
<ol>
<li>第一幅为第一次添加节点，此时 head 会延迟初始化；</li>
<li>第二幅图为已经存在队列，进行插入节点；</li>
<li>注意看代码，enq 方法返回的是<code>之前的尾节点</code>；</li>
<li>addWaiter 方法 返回的是<code>当前插入的新创建的节点</code>。</li>
</ol>
<p><img src= "https://cdn.jsdelivr.net/gh/liuzhihang/oss/pic/loading.gif" data-lazy-src="https://cdn.jsdelivr.net/gh/liuzhihang/oss/pic/article/aqs-addwaiter-1.png" alt="aqs-addwaiter-1"></p>
<p><img src= "https://cdn.jsdelivr.net/gh/liuzhihang/oss/pic/loading.gif" data-lazy-src="https://cdn.jsdelivr.net/gh/liuzhihang/oss/pic/article/cas-add-waiter-02.png" alt="cas-add-waiter-02"></p>
<p>节点添加到队列之后，返回当前节点，而下一步则需要调用方法 <code>acquireQueued(addWaiter(Node.EXCLUSIVE), arg)</code> 不断的去获取资源。</p>
<h6 id="出队列：acquireQueued-addWaiter-Node-EXCLUSIVE-arg"><a href="#出队列：acquireQueued-addWaiter-Node-EXCLUSIVE-arg" class="headerlink" title="出队列：acquireQueued(addWaiter(Node.EXCLUSIVE), arg)"></a>出队列：acquireQueued(addWaiter(Node.EXCLUSIVE), arg)</h6><p>方法会通过循环不断尝试获取拿到资源，直到成功。代码如下：</p>
<pre><code class="java">
final boolean acquireQueued(final Node node, int arg) &#123;
    // 是否拿到资源
    boolean failed = true;
    try &#123;
        // 中断状态
        boolean interrupted = false;
        // 无限循环
        for (;;) &#123;
            // 当前节点之前的节点
            final Node p = node.predecessor();
            // 前一个节点是头节点， 说明当前节点是 头节点的 next 即真实的第一个数据节点 （因为 head 是虚拟节点）
            // 然后再尝试获取资源
            if (p == head &amp;&amp; tryAcquire(arg)) &#123;
                // 获取成功之后 将头指针指向当前节点
                setHead(node); 
                p.next = null; // help GC
                failed = false;
                return interrupted;
            &#125;
            // p 不是头节点， 或者 头节点未能获取到资源 （非公平情况下被别的节点抢占） 
            // 判断 node 是否要被阻塞，
            if (shouldParkAfterFailedAcquire(p, node) &amp;&amp; parkAndCheckInterrupt())
                interrupted = true;
        &#125;
    &#125; finally &#123;
        if (failed)
            cancelAcquire(node);
    &#125;
&#125;</code></pre>
<ol>
<li>不断获取本节点的上一个节点是否为 head，因为 head 是虚拟节点，如果当前节点的上一个节点是 head 节点，则当前节点为 <code>第一个数据节点</code>；</li>
<li>第一个数据节点不断的去获取资源，获取成功，则将 head 指向当前节点；</li>
<li>当前节点不是头节点，或者 <code>tryAcquire(arg)</code> 失败（失败可能是非公平锁）。这时候需要判断前一个节点状态决定<code>当前节点是否要被阻塞</code>（前一个节点状态是否为 SIGNAL）。</li>
</ol>
<pre><code class="java">/**
 * 根据上一个节点的状态，判断当前线程是否应该被阻塞
 * SIGNAL -1 ：当前节点释放或者取消时，必须 unpark 他的后续节点。
 * CANCELLED 1 ：由于超时（timeout）或中断（interrupt），该节点被取消。节点永远不会离开此状态。特别是，具有取消节点的线程永远不会再次阻塞。
 * CONDITION -2 ：该节点目前在条件队列。 但它不会被用作同步队列节点，直到转移，转移时的状态将被设置为 0 。
 * PROPAGATE -3 ：releaseShared 应该被传播到其他节点。 
 * 0：都不是
 *
 */
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) &#123;
    // 前一个节点的等待状态
    int ws = pred.waitStatus;
    // 前一个节点需要 unpark 后续节点
    if (ws == Node.SIGNAL)
        return true;
    // 当前节点处于取消状态
    if (ws &gt; 0) &#123;
        do &#123;
            // 将取消的节点从队列中移除
            node.prev = pred = pred.prev;
        &#125; while (pred.waitStatus &gt; 0);
        pred.next = node;
    &#125; else &#123;
        // 设置前一个节点为 SIGNAL 状态
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    &#125;
    return false;
&#125;</code></pre>
<p>在 <code>shouldParkAfterFailedAcquire</code> 方法中，会判断前一个节点的状态，同时取消在队列中当前节点前面无效的节点。</p>
<p><img src= "https://cdn.jsdelivr.net/gh/liuzhihang/oss/pic/loading.gif" data-lazy-src="https://cdn.jsdelivr.net/gh/liuzhihang/oss/pic/article/aqs-acquire.png" alt="aqs-acquire"></p>
<p>再继续阅读 出队列 acquireQueued 方法，发现有一个 finally 会判断状态后执行 <code>cancelAcquire(node);</code> ，也就是上面流程图中下面的红色方块。</p>
<h6 id="cancelAcquire-Node-node"><a href="#cancelAcquire-Node-node" class="headerlink" title="cancelAcquire(Node node)"></a>cancelAcquire(Node node)</h6><pre><code class="java">
final boolean acquireQueued(final Node node, int arg) &#123;
    // 是否拿到资源
    boolean failed = true;
    try &#123;
        // 省略
        // 在 finally 会将当前节点置为取消状态
    &#125; finally &#123;
        if (failed)
            cancelAcquire(node);
    &#125;
&#125;


private void cancelAcquire(Node node) &#123;
    // 节点不存在 直接返回
    if (node == null)
        return;

    // 取消节点关联线程
    node.thread = null;

    //跳过已经取消的节点，获取当前节点之前的有效节点
    Node pred = node.prev;
    while (pred.waitStatus &gt; 0)
        node.prev = pred = pred.prev;

    // 获取当前节点之前的有效节点的下一个节点
    Node predNext = pred.next;

    // 当前节点设置为取消
    node.waitStatus = Node.CANCELLED;

    // 当前节点如果是尾节点，则将最后一个有效节点设置为尾节点，并将 predNext 设置为空
    if (node == tail &amp;&amp; compareAndSetTail(node, pred)) &#123;
        compareAndSetNext(pred, predNext, null);
    &#125; else &#123;
        int ws;
        // pred 不是头节点(node 的上一个有效节点 不是 head) &amp;&amp; （ pred的状态是 SIGNAL ||  pred 的状态设置为 SIGNAL 成功 ） &amp;&amp; pred 的绑定线程不为空
        if (pred != head &amp;&amp; 
        ((ws = pred.waitStatus) == Node.SIGNAL || (ws &lt;= 0 &amp;&amp; compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &amp;&amp; 
        pred.thread != null) &#123;
            // 当前节点的后继节点
            Node next = node.next;
            // 后继节点不为空 且 状态有效 将 pred 的 后继节点设置为 当前节点的后继节点
            if (next != null &amp;&amp; next.waitStatus &lt;= 0)
                compareAndSetNext(pred, predNext, next);
        &#125; else &#123;
            // node 的上一个有效节点 是 head， 或者其他情况 唤醒当前节点的下一个有效节点
            unparkSuccessor(node);
        &#125;

        node.next = node; // help GC
    &#125;
&#125;

private void unparkSuccessor(Node node) &#123;

    // 判断当前节点状态
    int ws = node.waitStatus;
    if (ws &lt; 0)
        // 将节点状态更新为 0 
        compareAndSetWaitStatus(node, ws, 0);

    // 下一个节点， 一般是下一个节点应该就是需要唤醒的节点，即颁发证书。
    Node s = node.next;
    // 大于 0  CANCELLED ： 线程已取消
    // 但是有可能 后继节点 为空或者被取消了。
    if (s == null || s.waitStatus &gt; 0) &#123;
        s = null;
        // 从尾节点开始遍历，直到定位到 t.waitStatus &lt;= 0 的节点
        // 定位到后并不会停止，会继续执行，相当于找到最开始的那个需要唤醒的节点
        // t.waitStatus &lt;= 0 ： SIGNAL（ -1 后续线程需要释放） 
        //                     CONDITION （ -2 线程正在等待条件） 
        //                     PROPAGATE （ -3 releaseShared 应该被传播到其他节点）
        for (Node t = tail; t != null &amp;&amp; t != node; t = t.prev)
            if (t.waitStatus &lt;= 0)
                s = t;
    &#125;
    // 定位到需要唤醒的节点后 进行 unpark
    if (s != null)
        LockSupport.unpark(s.thread);
&#125;
</code></pre>
<p>流程分析：</p>
<ol>
<li>找到当前节点的前一个非无效节点 pred；</li>
<li>当前节点如果是尾节点，则将最后一个有效节点设置为尾节点，并将 predNext 设置为空；</li>
<li>pred 不是头节点 &amp;&amp; （ pred的状态是 SIGNAL ||  pred 的状态设置为 SIGNAL 成功 ） &amp;&amp; pred 的绑定线程不为空；</li>
<li>其他情况。</li>
</ol>
<p>下面分别画图：</p>
<p><img src= "https://cdn.jsdelivr.net/gh/liuzhihang/oss/pic/loading.gif" data-lazy-src="https://cdn.jsdelivr.net/gh/liuzhihang/oss/pic/article/1-RD0LEx.png" alt="1-RD0LEx"></p>
<p><img src= "https://cdn.jsdelivr.net/gh/liuzhihang/oss/pic/loading.gif" data-lazy-src="https://cdn.jsdelivr.net/gh/liuzhihang/oss/pic/article/2-PHY9bi.png" alt="2-PHY9bi"></p>
<p><img src= "https://cdn.jsdelivr.net/gh/liuzhihang/oss/pic/loading.gif" data-lazy-src="https://cdn.jsdelivr.net/gh/liuzhihang/oss/pic/article/3-rOnnvu.png" alt="3-rOnnvu"></p>
<p><strong>Q: 通过图可以看出来，只操作了 next 指针，但是没有操作 prev 指针，这是为什么呢？</strong></p>
<p><strong>A:</strong> 在 <code>出队列：acquireQueued(addWaiter(Node.EXCLUSIVE), arg)</code> 方法中，<code>shouldParkAfterFailedAcquire</code> 方法会判断前一个节点的状态，同时取消在队列中当前节点前面无效的节点。这时候会移除之前的无效节点，此处也是为了防止指向一个已经被移除的节点。同时保证 prev 的稳定，有利于从 tail 开始遍历列表，这块在 <code>unparkSuccessor(node);</code> 中也可以看到是从后往前表里列表。</p>
<p><strong>Q: unparkSuccessor(Node node) 为什么从后往前遍历？</strong></p>
<p><strong>A:</strong></p>
<p><img src= "https://cdn.jsdelivr.net/gh/liuzhihang/oss/pic/loading.gif" data-lazy-src="https://cdn.jsdelivr.net/gh/liuzhihang/oss/pic/article/AQS-8IDBPX.png" alt="AQS-8IDBPX"></p>
<p>在 <code>addWaiter(Node.EXCLUSIVE)</code> 插入新节点时，使用的是 <code>尾插法</code>，看红框部分，此时有可能还未指向next。</p>
<p><strong>Q: node.next = node; 这块导致 head不是指向最新节点，链表不就断了么？</strong><br><strong>A：</strong> acquireQueued 方法介绍中，里面有个循环，会不断尝试获取资源，成功之后会设置为 head。并且在 shouldParkAfterFailedAcquire 中也会清除当前节点前的无效节点。</p>
<h5 id="释放独占资源-release"><a href="#释放独占资源-release" class="headerlink" title="释放独占资源 release"></a>释放独占资源 release</h5><pre><code class="java">public final boolean release(int arg) &#123;
    if (tryRelease(arg)) &#123;
        Node h = head;
        if (h != null &amp;&amp; h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    &#125;
    return false;
&#125;</code></pre>
<p>以独占模式释放。 通过释放一个或多个线程，如果实现tryRelease返回true。 这种方法可以用来实现方法Lock.unlock 。</p>
<ol>
<li>tryRelease(arg) 操作释放资源，同样是由子类实现，后面介绍子类时会进行说明。返回 true 说明资源现在已经没有线程持有了，其他节点可以尝试获取；</li>
<li>释放成功，且 head != null &amp;&amp; h.waitStatus != 0, 会继续执行 unparkSuccessor(h)；</li>
<li>这块会看到 只要 tryRelease(arg) 操作释放资源成功， 后面无论执行是否成功，都会返回 true，unparkSuccessor(h) 相当于只是附加操作。</li>
</ol>
<h4 id="共享模式"><a href="#共享模式" class="headerlink" title="共享模式"></a>共享模式</h4><h5 id="获取共享资源-acquireShared"><a href="#获取共享资源-acquireShared" class="headerlink" title="获取共享资源 acquireShared"></a>获取共享资源 acquireShared</h5><pre><code class="java">public final void acquireShared(int arg) &#123;
    // 小于 0 表示获取资源失败
    if (tryAcquireShared(arg) &lt; 0)
        doAcquireShared(arg);
&#125;

private void doAcquireShared(int arg) &#123;
    // 添加到节点 此处是共享节点
    final Node node = addWaiter(Node.SHARED);
    // 根据是否拿到资源 判断是否需要取消
    boolean failed = true;
    try &#123;
        boolean interrupted = false;
        for (;;) &#123;
            // 返回前一个节点
            final Node p = node.predecessor();
            if (p == head) &#123;
                // 再次尝试获取共享资源
                int r = tryAcquireShared(arg);
                // 表示获取成功
                if (r &gt;= 0) &#123;
                    // 设置当前节点为头节点 并尝试唤醒后续节点
                    setHeadAndPropagate(node, r);
                    // 释放头节点 GC 会回收
                    p.next = null; // help GC
                    if (interrupted)
                        selfInterrupt();
                    failed = false;
                    return;
                &#125;
            &#125;
            if (shouldParkAfterFailedAcquire(p, node) &amp;&amp;
                parkAndCheckInterrupt())
                interrupted = true;
        &#125;
    &#125; finally &#123;
        if (failed)
            cancelAcquire(node);
    &#125;
&#125;</code></pre>
<ol>
<li>tryAcquireShared(arg)，尝试获取资源，这块由子类实现；</li>
<li>返回值分为 3 种：<ol>
<li>小于 0: 表示失败；</li>
<li>等于 0: 表示共享模式获取资源成功，但后续的节点不能以共享模式获取成功; </li>
<li>大于 0: 表示共享模式获取资源成功，后续节点在共享模式获取也可能会成功，在这种情况下，后续等待线程必须检查可用性。</li>
</ol>
</li>
<li>在失败后会使用 <code>doAcquireShared(arg);</code> 不断获取资源；</li>
<li><code>final Node node = addWaiter(Node.SHARED);</code> 同样会创建节点；</li>
<li>在循环中不断判断前一个节点如果是 head，则尝试获取资源；</li>
<li>在共享模式下获取到资源后会使用 <code>setHeadAndPropagate(node, r);</code> 设置头节点，同时唤醒后续节点。</li>
</ol>
<h6 id="设置头节点，并传播唤醒后续节点"><a href="#设置头节点，并传播唤醒后续节点" class="headerlink" title="设置头节点，并传播唤醒后续节点"></a>设置头节点，并传播唤醒后续节点</h6><pre><code class="java">// node 是当前节点
// propagate 是 前一步 tryAcquireShared 的返回值 进来时 &gt;=0
// 大于 0: 表示共享模式获取资源成功，后续节点在共享模式获取也可能会成功，在这种情况下，后续等待线程必须检查可用性。
private void setHeadAndPropagate(Node node, int propagate) &#123;
    // 记录下当前头节点
    Node h = head; // Record old head for check below
    // 设置传入 node 为头节点
    setHead(node);
    // 判断条件，唤醒后续节点
    // propagate &gt; 0 有后续资源
    // h == null 旧的头节点 因为前面 addWaiter， 肯定不会为空，应该是防止 h.waitStatus &lt; 0 空指针的写法
    // (h = head) == null 当前的 头节点，再判断状态
    // waitStatus &lt; 0 后续节点就需要被唤醒
    if (propagate &gt; 0 || h == null || h.waitStatus &lt; 0 ||
        (h = head) == null || h.waitStatus &lt; 0) &#123;
        Node s = node.next;
        // 后续节点为共享，则需要唤醒
        if (s == null || s.isShared())
            doReleaseShared();
    &#125;
&#125;</code></pre>
<h6 id="doReleaseShared-释放共享资源"><a href="#doReleaseShared-释放共享资源" class="headerlink" title="doReleaseShared() 释放共享资源"></a>doReleaseShared() 释放共享资源</h6><pre><code class="java">
private void doReleaseShared() &#123;
    // 循环
    for (;;) &#123;
        // 从头开始
        Node h = head;
        // 判断队列是否为空，就是刚初始化
        if (h != null &amp;&amp; h != tail) &#123;
            int ws = h.waitStatus;
            // SIGNAL（ -1 后续线程需要释放）
            if (ws == Node.SIGNAL) &#123;
                // 将等待状态更新为 0 如果失败，会循环
                if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                    continue;            // loop to recheck cases
                // 唤醒后续节点， 同时将当前节点设置为 取消
                unparkSuccessor(h);
            &#125;
            // 如果状态是 0 则会更新状态为 PROPAGATE
            // PROPAGATE （ -3 releaseShared 应该被传播到其他节点）
            else if (ws == 0 &amp;&amp; !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                continue;                // loop on failed CAS
        &#125;
        // 判断头节点有没有变化，有变化 是因为竞争，别的线程获取到了锁，会继续循环
        // 没有变化直接结束
        if (h == head)                   // loop if head changed
            break;
    &#125;
&#125;</code></pre>
<ol>
<li>从头节点开始进行，如果 h != null &amp;&amp; h != tail 说明队列不是空或者刚初始化；</li>
<li>节点状态为 SIGNAL（ -1 ）说明后续线程需要释放；</li>
<li>会更改当前节点状态，成功后唤醒后续节点，失败则继续循环；</li>
<li>节点状态如果是 0 则更新为 PROPAGATE，会将状态传播。</li>
</ol>
<h5 id="释放共享资源-releaseShared"><a href="#释放共享资源-releaseShared" class="headerlink" title="释放共享资源 releaseShared"></a>释放共享资源 releaseShared</h5><pre><code class="java">public final boolean releaseShared(int arg) &#123;
    if (tryReleaseShared(arg)) &#123;
        // 释放共享资源
        doReleaseShared();
        return true;
    &#125;
    return false;
&#125;</code></pre>
<p>以共享模式释放。 通过释放一个或多个线程，如果实现tryReleaseShared返回true。</p>
<h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p><strong>Q: AQS 到底是什么？</strong><br><strong>A:</strong> AQS 内部提供了一个先入先出（FIFO）双向等待队列，内部依靠 Node 实现，并提供了在<code>独占模式</code>和<code>共享模式</code>下的出入队列的公共方法。而关于状态信息 state 的定义是由子类实现。tryAcquire、tryRelease、tryAcquireShared、tryReleaseShared等尝试获取资源操作都是由子类进行定义和实现的。而 AQS 中提供了子类获取资源之后的相关操作，包括节点 Node 的出入队列，自旋获取资源等等。</p>
<p><strong>Q: AQS 获取资源失败后会如何操作？</strong><br><strong>A:</strong> 线程获取资源失败后，会放到等待队列中，在队列中会不断尝试获取资源（自旋），说明线程只是进入等待状态，后面还是可以再次获取资源的。</p>
<p><strong>Q: AQS 等待队列的数据结构是什么？</strong><br><strong>A:</strong> CLH变体的先入先出（FIFO）双向等待队列。（CLH锁是一个自旋锁。能确保无饥饿性。提供先来先服务的公平性。是一种基于链表的可扩展、高性能、公平的自旋锁，申请线程仅仅在本地变量上自旋，它不断轮询前驱的状态，如果发现前驱释放了锁就结束自旋。）</p>
<p><strong>Q: AQS 等待队列中的节点如何获取获取和释放资源的？</strong><br><strong>A:</strong> 可以看下<code>独占模式</code>中的讲述过程，通过代码梳理。</p>
<p>本文分别从 <code>独占模式</code> 和 <code>共享模式</code>介绍的 AQS 基本逻辑，并通过源码和作图理解基本思路。但是并没有对需要子类实现的业务逻辑做介绍。这块会在后面介绍 <code>ReentrantLock</code>、<code>CountDownLatch</code> 等子类的时候做介绍。</p>
</article><div class="post-copyright"><div class="post-copyright__author"><span class="post-copyright-meta">文章作者: </span><span class="post-copyright-info"><a href="mailto:undefined">liuzhihang</a></span></div><div class="post-copyright__type"><span class="post-copyright-meta">文章链接: </span><span class="post-copyright-info"><a href="https://liuzhihang.com/2020/07/12/source-code-aqs.html">https://liuzhihang.com/2020/07/12/source-code-aqs.html</a></span></div><div class="post-copyright__notice"><span class="post-copyright-meta">版权声明: </span><span class="post-copyright-info">本博客所有文章除特别声明外，均采用 <a href="https://creativecommons.org/licenses/by-nc-sa/4.0/" target="_blank">CC BY-NC-SA 4.0</a> 许可协议。转载请注明来自 <a href="https://liuzhihang.com" target="_blank">程序员小航</a>！</span></div></div><div class="tag_share"><div class="post-meta__tag-list"><a class="post-meta__tags" href="/tags/%E6%BA%90%E7%A0%81%E7%AC%94%E8%AE%B0/">源码笔记</a><a class="post-meta__tags" href="/tags/JDK/">JDK</a></div><div class="post_share"><div class="social-share" data-image="https://cdn.jsdelivr.net/gh/liuzhihang/oss/pic/article/aqs-uhgQnu.png" data-sites="facebook,twitter,wechat,weibo,qq"></div><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/social-share.js/dist/css/share.min.css" media="print" onload="this.media='all'"><script src="https://cdn.jsdelivr.net/npm/social-share.js/dist/js/social-share.min.js" defer></script></div></div><nav class="pagination-post" id="pagination"><div class="prev-post pull-left"><a href="/2020/07/18/work-archetype.html"><img class="prev-cover" src= "https://cdn.jsdelivr.net/gh/liuzhihang/oss/pic/loading.gif" data-lazy-src="https://cdn.jsdelivr.net/gh/liuzhihang/oss/pic/article/archetype-qNA1BU.jpeg" onerror="onerror=null;src='https://cdn.jsdelivr.net/gh/liuzhihang/oss/pic/feature/92776_n5aac6.jpg'" alt="cover of previous post"><div class="pagination-info"><div class="label">上一篇</div><div class="prev_info">【工作笔记】- 老大说新项目的结构和 xxx 项目一样就可以了，我 ……</div></div></a></div><div class="next-post pull-right"><a href="/2020/07/05/source-code-locksupport.html"><img class="next-cover" src= "https://cdn.jsdelivr.net/gh/liuzhihang/oss/pic/loading.gif" data-lazy-src="https://cdn.jsdelivr.net/gh/liuzhihang/oss/pic/article/cover-locksupport.jpeg" onerror="onerror=null;src='https://cdn.jsdelivr.net/gh/liuzhihang/oss/pic/feature/92776_n5aac6.jpg'" alt="cover of next post"><div class="pagination-info"><div class="label">下一篇</div><div class="next_info">【JDK源码笔记】- JUC 包下工具类，它的名字叫 LockSupport ！你造么？</div></div></a></div></nav><div class="relatedPosts"><div class="headline"><i class="fas fa-thumbs-up fa-fw"></i><span> 相关推荐</span></div><div class="relatedPosts-list"><div><a href="/2020/09/27/source-code-arrayblockingqueue.html" title="【JDK源码笔记】- 基于数组的有界阻塞队列 —— ArrayBlockingQueue"><img class="cover" src= "https://cdn.jsdelivr.net/gh/liuzhihang/oss/pic/loading.gif" data-lazy-src="https://cdn.jsdelivr.net/gh/liuzhihang/oss/pic/article/ArrayBlockingQueue-cover-ESuNmo.jpeg" alt="cover"><div class="content is-center"><div class="date"><i class="far fa-calendar-alt fa-fw"></i> 2020-09-27</div><div class="title">【JDK源码笔记】- 基于数组的有界阻塞队列 —— ArrayBlockingQueue</div></div></a></div><div><a href="/2020/06/12/source-code-cas.html" title="【JDK源码笔记】- 从JUC源码看CAS，我做了个笔记 ......"><img class="cover" src= "https://cdn.jsdelivr.net/gh/liuzhihang/oss/pic/loading.gif" data-lazy-src="https://cdn.jsdelivr.net/gh/liuzhihang/oss/pic/article/cas.jpeg" alt="cover"><div class="content is-center"><div class="date"><i class="far fa-calendar-alt fa-fw"></i> 2020-06-12</div><div class="title">【JDK源码笔记】- 从JUC源码看CAS，我做了个笔记 ......</div></div></a></div><div><a href="/2020/10/25/source-code-concurrentlinkedqueue.html" title="【JDK源码笔记】- 非阻塞的无界线程安全队列 —— ConcurrentLinkedQueue"><img class="cover" src= "https://cdn.jsdelivr.net/gh/liuzhihang/oss/pic/loading.gif" data-lazy-src="https://cdn.jsdelivr.net/gh/liuzhihang/oss/pic/article/concurrentlinkedqueue-6pf8RL.jpeg" alt="cover"><div class="content is-center"><div class="date"><i class="far fa-calendar-alt fa-fw"></i> 2020-10-25</div><div class="title">【JDK源码笔记】- 非阻塞的无界线程安全队列 —— ConcurrentLinkedQueue</div></div></a></div></div></div><hr/><div id="post-comment"><div class="comment-head"><div class="comment-headline"><i class="fas fa-comments fa-fw"></i><span> 评论</span></div></div><div class="comment-wrap"><div><div id="gitalk-container"></div></div></div></div></div><div class="aside-content" id="aside-content"><div class="card-widget card-info"><div class="is-center"><div class="avatar-img"><img src= "https://cdn.jsdelivr.net/gh/liuzhihang/oss/pic/loading.gif" data-lazy-src="https://cdn.jsdelivr.net/gh/liuzhihang/oss/pic/404/avatar.jpg" onerror="this.onerror=null;this.src='/img/friend_404.gif'" alt="avatar"/></div><div class="author-info__name">liuzhihang</div><div class="author-info__description">学，然后知不足；教，然后知困。</div></div><div class="card-info-data"><div class="card-info-data-item is-center"><a href="/archives/"><div class="headline">文章</div><div class="length-num">145</div></a></div><div class="card-info-data-item is-center"><a href="/tags/"><div class="headline">标签</div><div class="length-num">55</div></a></div><div class="card-info-data-item is-center"><a href="/categories/"><div class="headline">分类</div><div class="length-num">31</div></a></div></div><a class="button--animated" id="card-info-btn" target="_blank" rel="noopener" href="https://github.com/liuzhihang"><i class="fab fa-github"></i><span>Follow Me</span></a><div class="card-info-social-icons is-center"><a class="social-icon" href="https://github.com/liuzhihang" target="_blank" title="Github"><i class="fab fa-github"></i></a><a class="social-icon" href="http://mail.qq.com/cgi-bin/qm_share?t=qm_mailme&amp;email=Vjo-Iyw_Pz43ODElFicneDU5Ow" target="_blank" title="Email"><i class="fas fa-envelope-open"></i></a><a class="social-icon" href="https://weibo.com/onlyhang" target="_blank" title="Weibo"><i class="fab fa-weibo"></i></a><a class="social-icon" href="https://twitter.com/liuzhihangs" target="_blank" title="Twitter"><i class="fab fa-twitter"></i></a><a class="social-icon" href="/atom.xml" target="_blank" title="RSS"><i class="fas fa-rss"></i></a><a class="social-icon" href="https://www.infoq.cn/u/liuzhihang/publish" target="_blank" title="InfoQ"><i class="iconfont iconweibiaoti-1"></i></a><a class="social-icon" href="https://juejin.im/user/1987506650493117" target="_blank" title="掘金"><i class="iconfont iconjuejin-1"></i></a><a class="social-icon" href="https://blog.csdn.net/qq_36535538" target="_blank" title="CSDN"><i class="iconfont iconcsdn"></i></a><a class="social-icon" href="https://www.zhihu.com/people/liuzhihang" target="_blank" title="知乎"><i class="iconfont iconzhihu1"></i></a><a class="social-icon" href="https://leetcode-cn.com/u/liuzhihang" target="_blank" title="LeetCode"><i class="iconfont iconleetcode"></i></a></div></div><div class="card-widget card-announcement"><div class="item-headline"><i class="fas fa-bullhorn card-announcement-animation"></i><span>公告</span></div><div class="announcement_content">🧑‍💻感谢访问本站，若喜欢请收藏 ^_^ <br> <br> <img src= "https://cdn.jsdelivr.net/gh/liuzhihang/oss/pic/loading.gif" data-lazy-src="https://cdn.jsdelivr.net/gh/liuzhihang/oss/pic/wechat.jpg"> <br> 个人公众号:『 程序员小航 』</div></div><div class="sticky_layout"><div class="card-widget" id="card-toc"><div class="item-headline"><i class="fas fa-stream"></i><span>目录</span></div><div class="toc-content"><ol class="toc"><li class="toc-item toc-level-3"><a class="toc-link" href="#%E4%BB%8B%E7%BB%8D"><span class="toc-text">介绍</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E9%98%9F%E5%88%97%E8%8A%82%E7%82%B9-Node"><span class="toc-text">队列节点 Node</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#AbstractQueuedSynchronizer"><span class="toc-text">AbstractQueuedSynchronizer</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#%E7%8B%AC%E5%8D%A0%E6%A8%A1%E5%BC%8F"><span class="toc-text">独占模式</span></a><ol class="toc-child"><li class="toc-item toc-level-5"><a class="toc-link" href="#%E8%8E%B7%E5%8F%96%E7%8B%AC%E5%8D%A0%E8%B5%84%E6%BA%90-acquire"><span class="toc-text">获取独占资源 acquire</span></a><ol class="toc-child"><li class="toc-item toc-level-6"><a class="toc-link" href="#%E5%85%A5%E9%98%9F%E5%88%97%EF%BC%9AaddWaiter-Node-EXCLUSIVE"><span class="toc-text">入队列：addWaiter(Node.EXCLUSIVE)</span></a></li><li class="toc-item toc-level-6"><a class="toc-link" href="#%E5%87%BA%E9%98%9F%E5%88%97%EF%BC%9AacquireQueued-addWaiter-Node-EXCLUSIVE-arg"><span class="toc-text">出队列：acquireQueued(addWaiter(Node.EXCLUSIVE), arg)</span></a></li><li class="toc-item toc-level-6"><a class="toc-link" href="#cancelAcquire-Node-node"><span class="toc-text">cancelAcquire(Node node)</span></a></li></ol></li><li class="toc-item toc-level-5"><a class="toc-link" href="#%E9%87%8A%E6%94%BE%E7%8B%AC%E5%8D%A0%E8%B5%84%E6%BA%90-release"><span class="toc-text">释放独占资源 release</span></a></li></ol></li><li class="toc-item toc-level-4"><a class="toc-link" href="#%E5%85%B1%E4%BA%AB%E6%A8%A1%E5%BC%8F"><span class="toc-text">共享模式</span></a><ol class="toc-child"><li class="toc-item toc-level-5"><a class="toc-link" href="#%E8%8E%B7%E5%8F%96%E5%85%B1%E4%BA%AB%E8%B5%84%E6%BA%90-acquireShared"><span class="toc-text">获取共享资源 acquireShared</span></a><ol class="toc-child"><li class="toc-item toc-level-6"><a class="toc-link" href="#%E8%AE%BE%E7%BD%AE%E5%A4%B4%E8%8A%82%E7%82%B9%EF%BC%8C%E5%B9%B6%E4%BC%A0%E6%92%AD%E5%94%A4%E9%86%92%E5%90%8E%E7%BB%AD%E8%8A%82%E7%82%B9"><span class="toc-text">设置头节点，并传播唤醒后续节点</span></a></li><li class="toc-item toc-level-6"><a class="toc-link" href="#doReleaseShared-%E9%87%8A%E6%94%BE%E5%85%B1%E4%BA%AB%E8%B5%84%E6%BA%90"><span class="toc-text">doReleaseShared() 释放共享资源</span></a></li></ol></li><li class="toc-item toc-level-5"><a class="toc-link" href="#%E9%87%8A%E6%94%BE%E5%85%B1%E4%BA%AB%E8%B5%84%E6%BA%90-releaseShared"><span class="toc-text">释放共享资源 releaseShared</span></a></li></ol></li></ol></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E6%80%BB%E7%BB%93"><span class="toc-text">总结</span></a></li></ol></div></div><div class="card-widget card-recent-post"><div class="item-headline"><i class="fas fa-history"></i><span>最新文章</span></div><div class="aside-list"><div class="aside-list-item"><a class="thumbnail" href="/2021/09/04/the_converter_converts_front_end_parameters_to_enumerations.html" title="使用 SpringBoot 转换器将前端参数转换为枚举"><img src= "https://cdn.jsdelivr.net/gh/liuzhihang/oss/pic/loading.gif" data-lazy-src="https://cdn.jsdelivr.net/gh/liuzhihang/oss/pic/article/uztio4-T5n5Wm.jpg" onerror="this.onerror=null;this.src='https://cdn.jsdelivr.net/gh/liuzhihang/oss/pic/feature/92776_n5aac6.jpg'" alt="使用 SpringBoot 转换器将前端参数转换为枚举"/></a><div class="content"><a class="title" href="/2021/09/04/the_converter_converts_front_end_parameters_to_enumerations.html" title="使用 SpringBoot 转换器将前端参数转换为枚举">使用 SpringBoot 转换器将前端参数转换为枚举</a><time datetime="2021-09-03T23:00:00.000Z" title="发表于 2021-09-04 07:00:00">2021-09-04</time></div></div><div class="aside-list-item"><a class="thumbnail" href="/2021/08/22/guide_to_mapper_overloading_crater_in_mybatis_plus.html" title="MyBatis-Plus 中 Mapper 重载踩坑指南"><img src= "https://cdn.jsdelivr.net/gh/liuzhihang/oss/pic/loading.gif" data-lazy-src="https://cdn.jsdelivr.net/gh/liuzhihang/oss/pic/article/ze1d2z-wXDWp0.jpg" onerror="this.onerror=null;this.src='https://cdn.jsdelivr.net/gh/liuzhihang/oss/pic/feature/92776_n5aac6.jpg'" alt="MyBatis-Plus 中 Mapper 重载踩坑指南"/></a><div class="content"><a class="title" href="/2021/08/22/guide_to_mapper_overloading_crater_in_mybatis_plus.html" title="MyBatis-Plus 中 Mapper 重载踩坑指南">MyBatis-Plus 中 Mapper 重载踩坑指南</a><time datetime="2021-08-21T23:00:00.000Z" title="发表于 2021-08-22 07:00:00">2021-08-22</time></div></div><div class="aside-list-item"><a class="thumbnail" href="/2021/07/25/tips_for_locking_and_optimizing_concurrent_scenes.html" title="并发场景加锁优化小技巧"><img src= "https://cdn.jsdelivr.net/gh/liuzhihang/oss/pic/loading.gif" data-lazy-src="https://cdn.jsdelivr.net/gh/liuzhihang/oss/pic/article/MODJ8j-9ObHeB.jpg" onerror="this.onerror=null;this.src='https://cdn.jsdelivr.net/gh/liuzhihang/oss/pic/feature/92776_n5aac6.jpg'" alt="并发场景加锁优化小技巧"/></a><div class="content"><a class="title" href="/2021/07/25/tips_for_locking_and_optimizing_concurrent_scenes.html" title="并发场景加锁优化小技巧">并发场景加锁优化小技巧</a><time datetime="2021-07-24T23:00:00.000Z" title="发表于 2021-07-25 07:00:00">2021-07-25</time></div></div><div class="aside-list-item"><a class="thumbnail" href="/2021/07/16/curator-5.html" title="ZooKeeper 分布式锁 Curator 源码 05：分布式读写锁和联锁"><img src= "https://cdn.jsdelivr.net/gh/liuzhihang/oss/pic/loading.gif" data-lazy-src="https://cdn.jsdelivr.net/gh/liuzhihang/oss/pic/article/EO0tci-8Ju4bN.png" onerror="this.onerror=null;this.src='https://cdn.jsdelivr.net/gh/liuzhihang/oss/pic/feature/92776_n5aac6.jpg'" alt="ZooKeeper 分布式锁 Curator 源码 05：分布式读写锁和联锁"/></a><div class="content"><a class="title" href="/2021/07/16/curator-5.html" title="ZooKeeper 分布式锁 Curator 源码 05：分布式读写锁和联锁">ZooKeeper 分布式锁 Curator 源码 05：分布式读写锁和联锁</a><time datetime="2021-07-16T13:30:30.000Z" title="发表于 2021-07-16 21:30:30">2021-07-16</time></div></div><div class="aside-list-item"><a class="thumbnail" href="/2021/07/15/curator-4.html" title="ZooKeeper 分布式锁 Curator 源码 04：分布式信号量和互斥锁"><img src= "https://cdn.jsdelivr.net/gh/liuzhihang/oss/pic/loading.gif" data-lazy-src="https://cdn.jsdelivr.net/gh/liuzhihang/oss/pic/article/EO0tci-8Ju4bN.png" onerror="this.onerror=null;this.src='https://cdn.jsdelivr.net/gh/liuzhihang/oss/pic/feature/92776_n5aac6.jpg'" alt="ZooKeeper 分布式锁 Curator 源码 04：分布式信号量和互斥锁"/></a><div class="content"><a class="title" href="/2021/07/15/curator-4.html" title="ZooKeeper 分布式锁 Curator 源码 04：分布式信号量和互斥锁">ZooKeeper 分布式锁 Curator 源码 04：分布式信号量和互斥锁</a><time datetime="2021-07-15T13:30:30.000Z" title="发表于 2021-07-15 21:30:30">2021-07-15</time></div></div></div></div></div></div></main><footer id="footer"><div id="footer-wrap"><div class="copyright">&copy;2017 - 2021 By liuzhihang</div><div class="framework-info"><span>框架 </span><a target="_blank" rel="noopener" href="https://hexo.io">Hexo</a><span class="footer-separator">|</span><span>主题 </span><a target="_blank" rel="noopener" href="https://github.com/jerryc127/hexo-theme-butterfly">Butterfly</a></div><div class="footer_custom_text"><a target="_blank" rel="noopener" href="http://www.beian.miit.gov.cn/"><img class="icp-icon" src= "https://cdn.jsdelivr.net/gh/liuzhihang/oss/pic/loading.gif" data-lazy-src="/resources/image/icp.png"><span>备案号：京ICP备20000888号</span></a></div></div></footer></div><div id="rightside"><div id="rightside-config-hide"><button id="readmode" type="button" title="阅读模式"><i class="fas fa-book-open"></i></button><button id="translateLink" type="button" title="简繁转换">繁</button><button id="darkmode" type="button" title="浅色和深色模式转换"><i class="fas fa-adjust"></i></button><button id="hide-aside-btn" type="button" title="单栏和双栏切换"><i class="fas fa-arrows-alt-h"></i></button></div><div id="rightside-config-show"><button id="rightside_config" type="button" title="设置"><i class="fas fa-cog fa-spin"></i></button><button class="close" id="mobile-toc-button" type="button" title="目录"><i class="fas fa-list-ul"></i></button><a id="to_comment" href="#post-comment" title="直达评论"><i class="fas fa-comments"></i></a><button id="go-up" type="button" title="回到顶部"><i class="fas fa-arrow-up"></i></button></div></div><div id="local-search"><div class="search-dialog"><div class="search-dialog__title" id="local-search-title">本地搜索</div><div id="local-input-panel"><div id="local-search-input"><div class="local-search-box"><input class="local-search-box--input" placeholder="搜索文章" type="text"/></div></div></div><hr/><div id="local-search-results"></div><span class="search-close-button"><i class="fas fa-times"></i></span></div><div id="search-mask"></div></div><div><script src="/js/utils.js"></script><script src="/js/main.js"></script><script src="/js/tw_cn.js"></script><script src="https://cdn.jsdelivr.net/npm/instant.page/instantpage.min.js" type="module"></script><script src="https://cdn.jsdelivr.net/npm/vanilla-lazyload/dist/lazyload.iife.min.js"></script><script src="/js/search/local-search.js"></script><div class="js-pjax"><script>if (document.getElementsByClassName('mermaid').length) {
  if (window.mermaidJsLoad) mermaid.init()
  else {
    getScript('https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.min.js').then(() => {
      window.mermaidJsLoad = true
      mermaid.initialize({
        theme: 'default',
      })
      false && mermaid.init()
    })
  }
}</script><script>function addGitalkSource () {
  const ele = document.createElement('link')
  ele.rel = 'stylesheet'
  ele.href= 'https://cdn.jsdelivr.net/npm/gitalk/dist/gitalk.min.css'
  document.getElementsByTagName('head')[0].appendChild(ele)
}

function loadGitalk () {
  function initGitalk () {
    var gitalk = new Gitalk(Object.assign({
      clientID: 'e48f300349e2ac2d03bd',
      clientSecret: '001c669e6269dfd9d7d9fef029cc71fd00ea69e3',
      repo: 'comments',
      owner: 'liuzhihang',
      admin: ['liuzhihang'],
      id: 'a76031b672cf2998e74c3cfcd67e097a',
      language: 'en',
      perPage: 10,
      distractionFreeMode: false,
      pagerDirection: 'last',
      createIssueManually: false,
      updateCountCallback: commentCount
    },null))

    gitalk.render('gitalk-container')
  }

  if (typeof Gitalk === 'function') initGitalk()
  else {
    addGitalkSource()
    getScript('https://cdn.jsdelivr.net/npm/gitalk@latest/dist/gitalk.min.js').then(initGitalk)
  }
}

function commentCount(n){
  let isCommentCount = document.querySelector('#post-meta .gitalk-comment-count')
  if (isCommentCount) {
    isCommentCount.innerHTML= n
  }
}

if ('Gitalk' === 'Gitalk' || !false) {
  if (false) btf.loadComment(document.getElementById('gitalk-container'), loadGitalk)
  else loadGitalk()
} else {
  function loadOtherComment () {
    loadGitalk()
  }
}</script></div><script async data-pjax src="//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script></div></body></html>